﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Audio;

namespace Gakita.View
{
    /* This class handles drawing the game, playing sounds, and reading user input */
    class GameView : RabbitPlayerDeathListener
    {
        public enum PlayerDirection
        {
            upperleft = 1,
            left = 3,
            lowerleft = 5,
            notMoving = 0,
            up = 0,
            down = 0,
            upperright = 2,
            right = 0,
            lowerright = 4
        }

        private static Keys _jumpKey                        =       Keys.Space;
        private static Keys _leftKey                        =       Keys.Left;
        private static Keys _rightKey                       =       Keys.Right;

        private SpriteBatch _spriteBatch;
        private KeyboardState _oldKeyboardState;
        private Texture2D _playerTexture;
        private Texture2D _cakeTexture;
        private Texture2D _rabbitTexture;
        private Texture2D _giantRabbitTexture;
        private Texture2D _spikyThingTexture;
        private Texture2D _flyingEnemyTexture;
        private Texture2D _flyingPlatform;
        private Texture2D _tileTexture;
        private Texture2D _backgroundTexture;
        private Texture2D _bloodTexture;
        private SpriteFont _gameFont;
        private Song _backgroundMusic;
        private SoundEffect _bounceSoundThree;
        private SoundEffect _catDeathSound;

        private bool particleSystemIsActive                 =       false;
        ParticleSystem _bloodParticleSystem                 =       null;

        private float _timeSinceSpikeTextureChanged         =       0;
        private int _spikeTextureToUse                      =       0;

        private int _textureTileSize                        =       128;

        private static bool _backgroundMusicIsPlaying       =       false;

        public GameView(GraphicsDeviceManager aManager, ContentManager aContentManager, SpriteBatch _aSpriteBatch)
        {
            _spriteBatch = _aSpriteBatch;

            _playerTexture = aContentManager.Load<Texture2D>("playerSprite");
            _cakeTexture = aContentManager.Load<Texture2D>("aCakeTexture");
            _tileTexture = aContentManager.Load<Texture2D>("tiles");
            _rabbitTexture = aContentManager.Load<Texture2D>("rabbitSprites");
            _giantRabbitTexture = aContentManager.Load<Texture2D>("GiantRabbit");
            _spikyThingTexture = aContentManager.Load<Texture2D>("SpikyThingSprites");
            _flyingEnemyTexture = aContentManager.Load<Texture2D>("FlyingEnemy");
            _backgroundTexture = aContentManager.Load<Texture2D>("background");
            _flyingPlatform = aContentManager.Load<Texture2D>("FlyingPlatform");
            _bloodTexture = aContentManager.Load<Texture2D>("Blood");
            _gameFont = aContentManager.Load<SpriteFont>("GameFont");
            _backgroundMusic = aContentManager.Load<Song>("snapsphere");
            _bounceSoundThree = aContentManager.Load<SoundEffect>("Hop");
            _catDeathSound = aContentManager.Load<SoundEffect>("Cat");

            MediaPlayer.IsRepeating = true;
        }

        #region Draw

        public void DrawEverything(GraphicsDevice aGraphicsDevice, Model.Level aLevel, Camera aCamera, Vector2 aPlayerPosition,                                                int aLivesLeft, float aElapsedTime, Vector2 aPlayerSpeed, int aTime, bool aIsGamePausedAfterDeath)
        {
            // If the ingame music is not playing, we stop the menu music and start the ingame music.
            if (_backgroundMusicIsPlaying == false)
            {
                MediaPlayer.Play(_backgroundMusic);
                _backgroundMusicIsPlaying = true;
                Menu.MenuView.StopMenuMusic();
            }

            aGraphicsDevice.Clear(new Microsoft.Xna.Framework.Color(142, 255, 255));

            Vector2 viewportSize = new Vector2(aGraphicsDevice.Viewport.Width, aGraphicsDevice.Viewport.Height);
            float scale = aCamera.GetScale();

            _spriteBatch.Begin();

            Vector2 topLeftCornerOfScreenModelPosition = aCamera.GetModelPosition(0, 0, viewportSize);
            DrawBackground(viewportSize, topLeftCornerOfScreenModelPosition, scale);

            // We retrive the view position for the cake and use it to draw.
            Vector2 cakeViewPos = aCamera.GetViewPosition(aLevel.GetCakePosition().X, aLevel.GetCakePosition().Y, viewportSize);
            DrawCake(cakeViewPos, scale);

            foreach (Model.Enemies.FlyingEnemy flyingEnemy in aLevel._flyingEnemies)
            {
                // Get the view position and draw the flying enemy
                Vector2 modelPosition = flyingEnemy.GetPosition();
                Vector2 enemyViewPosition = aCamera.GetViewPosition(modelPosition.X, modelPosition.Y, viewportSize);
                DrawFlyingEnemy(enemyViewPosition, scale);
            }
            
            // Get the player view position and direction, then draws the player
            Vector2 playerViewPos = aCamera.GetViewPosition(aPlayerPosition.X, aPlayerPosition.Y, viewportSize);
            PlayerDirection Direction = DeterminePlayerDirection(aPlayerSpeed);
            DrawPlayer(playerViewPos, scale, Direction);

            foreach (Model.Miscellaneous.FlyingPlatform flyingPlatform in aLevel._flyingPlatforms)
            {
                // Get the view position then draw the flying platforms
                Vector2 modelPosition = flyingPlatform.GetPosition();
                Vector2 platformViewPosition = aCamera.GetViewPosition(modelPosition.X, modelPosition.Y, viewportSize);
                DrawFlyingPlatform(platformViewPosition, scale);
            }

            foreach (Model.Enemies.Rabbit rabbit in aLevel._rabbits)
            {
                // Get the view position and draw the rabbit
                Vector2 modelPosition = rabbit.GetPosition();
                Vector2 enemyViewPosition = aCamera.GetViewPosition(modelPosition.X, modelPosition.Y, viewportSize);
                DrawRabbit(enemyViewPosition, scale, rabbit.GetSpeed(), rabbit.IsAlive());
            }


            // Go through the level and draw each tile
            for (int x = 0; x < Model.Level._levelWidth; x++)
            {
                for (int y = 0; y < Model.Level._levelHeight; y++)
                {
                    Vector2 viewPos = aCamera.GetViewPosition(x, y, viewportSize);
                    DrawTile(viewPos.X, viewPos.Y, scale, aLevel._tiles[x, y]);
                }
            }

            foreach (Model.Enemies.SpikyThing spikyThing in aLevel._spikyThings)
            {
                // Get the view position then draw the spiky things
                Vector2 modelPosition = spikyThing.GetPosition();
                Vector2 enemyViewPosition = aCamera.GetViewPosition(modelPosition.X, modelPosition.Y, viewportSize);
                DrawSpikyThing(enemyViewPosition, scale, aElapsedTime);
            }

            foreach (Model.Enemies.GiantRabbit gRabbit in aLevel._giantRabbits)
            {
                // Get the view position and draw the rabbit
                Vector2 modelPosition = gRabbit.GetPosition();
                // Drawn slightly below his position, because it looks better
                modelPosition.Y += 0.02f;
                Vector2 enemyViewPosition = aCamera.GetViewPosition(modelPosition.X, modelPosition.Y, viewportSize);
                DrawGiantRabbit(enemyViewPosition, scale);
            }


            // If the player have died and the game is paused, we draw information telling the player 
            // that he's dead and how long it will take for him to respawn
            if (aIsGamePausedAfterDeath)
            {
                DrawPlayerDied(viewportSize);
            }
            // If the game is paused and the player only got 1 life left, it means he will die once the pause is over. 
            // So we tell the player that the game is over
            if (aIsGamePausedAfterDeath == true && aLivesLeft == 1)
            {
                DrawGameOver(viewportSize);
            }
            else
            {
                DrawTime(viewportSize, aTime);

                DrawPlayerLivesLeft(viewportSize, aLivesLeft);
            }

            if (particleSystemIsActive == true)
            {
                _bloodParticleSystem.Draw(_spriteBatch, _bloodTexture, aElapsedTime, aCamera, viewportSize);
            }

            _spriteBatch.End();
        }

        private void DrawGameOver(Vector2 aViewportSize)
        {
            String text = "Game Over";
            float scale = 2.0f;
            Vector2 stringPosition = new Vector2(aViewportSize.X / 2 - (_gameFont.MeasureString(text).X / 2) * scale, 
                                                 aViewportSize.Y / 2.2f);

            _spriteBatch.DrawString(_gameFont, text, stringPosition, Color.Black,0,new Vector2(0,0),scale,SpriteEffects.None,0);
        }

        private void DrawGiantRabbit(Vector2 aEnemyViewBottomCenterPosition, float aScale)
        {
            Rectangle destinationRectangle = new Rectangle((int)(aEnemyViewBottomCenterPosition.X - (aScale * 3 / 2)),
                                                           (int)(aEnemyViewBottomCenterPosition.Y - (aScale * 5)), 
                                                           (int)(aScale * 3), (int)(aScale * 5));

            _spriteBatch.Draw(_giantRabbitTexture, destinationRectangle, Color.White);
        }

        private void DrawFlyingPlatform(Vector2 aPlatformTopLeftViewPosition, float aScale)
        {
            Rectangle destinationRectangle = new Rectangle((int)aPlatformTopLeftViewPosition.X,                                                                                                         (int)aPlatformTopLeftViewPosition.Y,(int)aScale * 2, (int)aScale);

            _spriteBatch.Draw(_flyingPlatform, destinationRectangle, Color.White);
        }

        private void DrawPlayerDied(Vector2 aViewportSize)
        {
            String text = "You Died!";

            Vector2 stringPosition = new Vector2(aViewportSize.X / 2 - _gameFont.MeasureString(text).X / 2, aViewportSize.Y / 3);

            _spriteBatch.DrawString(_gameFont, text, stringPosition, Color.Red);
        }


        private void DrawTile(float aXPosition, float aYPosition, float aScale, Model.Level.Tile aTile)
        {
            Rectangle sourceRectangle = new Rectangle(_textureTileSize * (int)aTile, 0, _textureTileSize, _textureTileSize);
        
            Rectangle destRect = new Rectangle((int)aXPosition, (int)aYPosition, (int)aScale, (int)aScale);

            _spriteBatch.Draw(_tileTexture, destRect, sourceRectangle, Color.White);
        }

        private void DrawPlayer(Microsoft.Xna.Framework.Vector2 aViewBottomCenterPosition, float aScale, PlayerDirection aDirection)
        {
            Rectangle destinationRectangle = new Rectangle((int)(aViewBottomCenterPosition.X - aScale / 2.0f),                                                                                          (int)(aViewBottomCenterPosition.Y - aScale), (int)aScale, (int)aScale);

            Rectangle sourceRectangle = new Rectangle(_textureTileSize * (int)aDirection, 0, _textureTileSize, _textureTileSize);

            _spriteBatch.Draw(_playerTexture, destinationRectangle, sourceRectangle, Color.White);
        
        }

        private void DrawCake(Vector2 aViewBottomCenterPosition, float aScale)
        {
            Rectangle destinationRectangle = new Rectangle((int)(aViewBottomCenterPosition.X - aScale / 2.0f),                                                                                          (int)(aViewBottomCenterPosition.Y - aScale), (int)aScale, (int)aScale);

            _spriteBatch.Draw(_cakeTexture, destinationRectangle, Color.White);
        }

        /* This method draws the static background image used for all levels */
        private void DrawBackground(Vector2 aViewportSize, Vector2 aTopLeftCornerOfTheScreenModelPosition, float aScale)
        {
            int sourceX = (int)(aScale / 4 * aTopLeftCornerOfTheScreenModelPosition.X);
            int sourceY = (int)(aScale / 4 * aTopLeftCornerOfTheScreenModelPosition.Y);

            // We want to draw it on the entire screen
            Rectangle destinationRectangle = new Rectangle(0, 0, (int)aViewportSize.X, (int)aViewportSize.Y);

            // Get the correct part of the background to draw
            Rectangle sourceRectangle = new Rectangle(sourceX,sourceY,(int) aViewportSize.X/4,(int) aViewportSize.Y/4);

            _spriteBatch.Draw(_backgroundTexture, destinationRectangle, sourceRectangle, Color.White);
        }

        private void DrawRabbit(Vector2 aViewBottomCenterPosition, float aScale, Vector2 aSpeed, bool aIsAlive)
        {
            // Check the speed to determine which direction the rabbit is moving and then use the correct texture
            int TextureToUse = 0;

            if (aSpeed.X > 0)
            {
                TextureToUse = 1;
            }
            // If the rabbit is dead
            if (aIsAlive == false)
            {
                if (aSpeed.X < 0)
                {
                    TextureToUse = 2;
                }
                else
                {
                    TextureToUse = 3;
                }
            }

            Rectangle sourceRectangle = new Rectangle(_textureTileSize * TextureToUse, 0, _textureTileSize, _textureTileSize);

            Rectangle destinationRectangle = new Rectangle((int)(aViewBottomCenterPosition.X - aScale / 2.0f),                                                                                          (int)(aViewBottomCenterPosition.Y - aScale), (int)aScale, (int)aScale);

            _spriteBatch.Draw(_rabbitTexture, destinationRectangle, sourceRectangle, Color.White);
        }

        private void DrawSpikyThing(Vector2 aViewCenterPosition, float aScale, float aElapsedTime)
        {
            // Since the spiky things are supposed to rotate, we change its' texture every 0.18 seconds.
            _timeSinceSpikeTextureChanged += aElapsedTime;

            if(_timeSinceSpikeTextureChanged > 0.18)
            {
                _spikeTextureToUse++;
                _timeSinceSpikeTextureChanged = 0;
            }
            // Since there are 10 different textures, we start again once we reach the last one
            if(_spikeTextureToUse == 10)
            {
                _spikeTextureToUse = 0;
            }

            Rectangle sourceRectangle = new Rectangle(_textureTileSize * _spikeTextureToUse, 0, _textureTileSize, _textureTileSize);

            Rectangle destinationRectangle = new Rectangle((int)(aViewCenterPosition.X - aScale / 2.0f),                                                                                                (int)(aViewCenterPosition.Y - aScale / 2.0f), (int)aScale, (int)aScale);

            _spriteBatch.Draw(_spikyThingTexture, destinationRectangle, sourceRectangle, Color.White);
        }

        private void DrawFlyingEnemy(Vector2 aViewBottomCenterPosition, float aScale)
        {
            Rectangle sourceRectangle = new Rectangle(0, 0, _textureTileSize, _textureTileSize);

            Rectangle destinationRectangle = new Rectangle((int)(aViewBottomCenterPosition.X - aScale / 2.0f),                                                                                          (int)(aViewBottomCenterPosition.Y - aScale), (int)aScale, (int)aScale);

            _spriteBatch.Draw(_flyingEnemyTexture, destinationRectangle, sourceRectangle, Color.White);
        }

        private void DrawTime(Vector2 aViewPortSize, int aTime)
        {
            string timeString = "Time Played: ";

            // We convert the time from milliseconds to seconds and cut it to an appropriate length.
            if (aTime > 10000)
            {
                timeString += ((float)aTime / 1000).ToString().Substring(0, 5);
            }
            else if (aTime < 0)
            {
                // If the player is dead, we don't display the time played, but instead the time that's left until the player respawns
                timeString = "Time until respawn: " + ((float)aTime / 1000).ToString().Substring(1, 1);
            }
            else
            {
                timeString += 0 + ((float)aTime / 1000).ToString().Substring(0, 4);
            }
            
            // As a standard, time is drawn at the top of the screen.
            Vector2 timePosition = new Vector2(aViewPortSize.X / 2 - _gameFont.MeasureString(timeString).X / 2, 0);

            // When dead, it's drawn in the middle of the screen.
            if (aTime < 0)
            {
                timePosition.Y = aViewPortSize.Y / 2 - _gameFont.MeasureString(timeString).Y / 2;
            }

            _spriteBatch.DrawString(_gameFont, timeString, timePosition, Color.Black);
        }

        /* This method draws a player texture and a number indicating how many lives are left in the upper left corner of the screen */
        private void DrawPlayerLivesLeft(Vector2 aViewPortSize, int aLivesLeft)
        {
            String livesString = "x" + aLivesLeft;

            Rectangle destinationRectangle = new Rectangle(0, 0, (int)(_textureTileSize / 2.5f), (int)(_textureTileSize / 2.5f));

            Rectangle sourceRectangle = new Rectangle(0, 0, _textureTileSize, _textureTileSize);

            _spriteBatch.Draw(_playerTexture, destinationRectangle, sourceRectangle, Color.White);

            _spriteBatch.DrawString(_gameFont, livesString, new Vector2((int)(_textureTileSize / 2.5f),                                                                          (int)(_textureTileSize / 2.5f) - _gameFont.MeasureString(livesString).Y), Color.Black);

        }

        #endregion

        #region Input
        public bool DidPlayerPressLeft()
        {
            return _oldKeyboardState.IsKeyDown(_leftKey);
        }

        public bool DidPlayerPressRight()
        {
            return _oldKeyboardState.IsKeyDown(_rightKey);
        }

        public bool DidPlayerPressMenu()
        {
            return _oldKeyboardState.IsKeyDown(Keys.Escape);
        }

        public bool DidPlayerPressJump()
        {
            bool ret = false;
            KeyboardState newKeyboardState = Keyboard.GetState();

            if(_oldKeyboardState.IsKeyUp(_jumpKey) && newKeyboardState.IsKeyDown(_jumpKey) )
            {
                ret = true;
            }

            _oldKeyboardState = newKeyboardState;

            return ret;
        }

        public static void SetJumpKey(Keys akey)
        {
            _jumpKey = akey;
        }

        public static void SetLeftKey(Keys akey)
        {
            _leftKey = akey;
        }

        public static void SetRightKey(Keys akey)
        {
            _rightKey = akey;
        }

        public static Keys GetJumpKey()
        {
            return _jumpKey;
        }

        public static Keys GetLeftKey()
        {
            return _leftKey;
        }

        public static Keys GetRightKey()
        {
            return _rightKey;
        }

        #endregion

        /* Method for checking which direction the player is moving. Used for picking an appropriate texture for the player */
        private PlayerDirection DeterminePlayerDirection(Vector2 aPlayerSpeed)
        {
            if (aPlayerSpeed.X > 0)
            {
                if(aPlayerSpeed.Y > 5)
                {
                    return PlayerDirection.lowerright;
                }
                else if (aPlayerSpeed.Y < -2)
                {
                    return PlayerDirection.upperright;
                }
                else
                {
                    return PlayerDirection.right;
                }
            }
            else if (aPlayerSpeed.X < 0)
            {
                if (aPlayerSpeed.Y > 5)
                {
                    return PlayerDirection.lowerleft;
                }
                else if (aPlayerSpeed.Y < -2)
                {
                    return PlayerDirection.upperleft;
                }
                else
                {
                    return PlayerDirection.left;
                }
            }
            else
            {
                if (aPlayerSpeed.Y > 0)
                {
                    return PlayerDirection.up;
                }
                else if (aPlayerSpeed.Y < 0)
                {
                    return PlayerDirection.down;
                }
                else
                {
                    return PlayerDirection.notMoving;
                }
            }
        }

        internal static void StopBackgroundMusic()
        {
            _backgroundMusicIsPlaying = false;
        }

        private void PlayBounceSound()
        {
            _bounceSoundThree.Play();
        }

        public void RabbitIsDead(Model.Enemies.Rabbit aRabbit)
        {
            PlayBounceSound();

            // When a rabbit dies, a particle system is created at the position of the rabbits head.
            particleSystemIsActive = true;

            Vector2 particleSystemPosition = Vector2.Zero;

            if (aRabbit.GetSpeed().X > 0)
            {
                particleSystemPosition = aRabbit.GetPosition() + new Vector2(0.4f,-0.5f);
            }
            else
            {
                particleSystemPosition = aRabbit.GetPosition() + new Vector2(-0.25f,-0.5f);
            }

            _bloodParticleSystem = new ParticleSystem(particleSystemPosition);
        }

        public void PlayerIsDead()
        {
            _catDeathSound.Play();
        }

    }
}
